﻿using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Threading;

namespace CubeCalculations
{
   public unsafe class OptimalCubeSolver
   {

      const int N_TWIST = 18;

      const int MAX_TWISTS = 43;
      private enum OptimalCubeSteps
      {
         //"F ", "F2", "F'", "R ", "R2", "R'",
         //                                 "U ", "U2", "U'", "B ", "B2", "B'",
         //                                 "L ", "L2", "L'", "D ", "D2", "D'"
         F = 0,
         F2,
         Fc,
         R,
         R2,
         Rc,
         U,
         U2,
         Uc,
         B,
         B2,
         Bc,
         L,
         L2,
         Lc,
         D,
         D2,
         Dc
      }

      private enum CubeStepMatch
      {
         UL = 0,
         UR,
         LU,
         LD,
         FA,
         FC,
         RU,
         RD,
         DL,
         DR,
         BA,
         BC
      }


      public struct Cube
      {
         public fixed int edges[24];
         public fixed int corners[24];
      }

      public struct Metric_data
      {
         public int metric;
         public char metric_char;


         public fixed int twist_length[N_TWIST];
         public int increment;
      }


      public struct Options
      {
         public int use_symmetry;
         public int search_limit;
         public int one_solution_only;
      }

      public event EventHandler OnReady;

      private void InvokeOnReady()
      {
         EventHandler onReadyHandler = OnReady;
         if (onReadyHandler != null) onReadyHandler(this, EventArgs.Empty);
      }

      [DllImport("OptimalCubeSolver.dll")]
      static extern void init_globals();
      [DllImport("OptimalCubeSolver.dll")]
      static extern int main();

      [DllImport("OptimalCubeSolver.dll")]
      static extern void init_options(ref Metric_data p_metric_data, ref Options p_user_options);


      [DllImport("OptimalCubeSolver.dll")]
      static extern int save_globals(string Filename);

      [DllImport("OptimalCubeSolver.dll")]
      static extern int restore_globals(string Filename);


      [DllImport("OptimalCubeSolver.dll")]
      static extern int string_to_cube(string input, ref Cube p_cube, int give_err_msg);

      [DllImport("OptimalCubeSolver.dll")]
      private static extern int solve_cube(ref Cube p_cube, Options cOptions, ref int SolutionLength, int[] Solution);


      [DllImport("OptimalCubeSolver.dll")]
      static extern void Interrupt();

      readonly Metric_data metric_data;
      Options user_options;

      private Thread SolutionThread;
      private static bool GlobalsInitialized;

      public OptimalCubeSolver()
      {
         init_options(ref metric_data, ref user_options);
      }

      void InitCube()
      {
         if (!GlobalsInitialized)
         {
            int RestoreSuccess = restore_globals("globals.bin");
            if (RestoreSuccess != 0)
            {
               init_globals();
               save_globals("globals.bin");
            }

            GlobalsInitialized = true;
         }
         InvokeOnReady();
      }
      public void BeginInit()
      {
         SolutionThread = new Thread(InitCube);
         SolutionThread.Start();
      }

      public bool Solve(string CubeString, int MaxSolutionSearch, out List<int> SolutionList)
      {
         SolutionList = new List<int>();

         Cube cube;
         string_to_cube(CubeString, ref cube, 1);
         user_options.search_limit = MaxSolutionSearch;

         var Solution = new int[MAX_TWISTS];
         int SolLen = 0;
         int SolutionFnd = solve_cube(ref cube, user_options, ref SolLen, Solution);
         if (SolutionFnd > 0)
         {
            SolutionList.AddRange(OptToCubexSolution(Solution, SolLen));
         }
         return SolutionFnd > 0;
      }

      static List<int> OptToCubexSolution(int[] SolutionList, int SolutionLength)
      {
         var Solution = new List<int>(SolutionLength * 2);
         for (int i = 0; i < SolutionLength; i++)
         {
            switch ((OptimalCubeSteps)SolutionList[i])
            {
               case OptimalCubeSteps.F2:
                  Solution.Add((int)CubeStepMatch.FC);
                  Solution.Add((int)CubeStepMatch.FC);
                  break;
               case OptimalCubeSteps.F:
                  Solution.Add((int)CubeStepMatch.FC);
                  break;
               case OptimalCubeSteps.Fc:
                  Solution.Add((int)CubeStepMatch.FA);
                  break;
               case OptimalCubeSteps.R2:
                  Solution.Add((int)CubeStepMatch.RU);
                  Solution.Add((int)CubeStepMatch.RU);
                  break;
               case OptimalCubeSteps.R:
                  Solution.Add((int)CubeStepMatch.RU);
                  break;
               case OptimalCubeSteps.Rc:
                  Solution.Add((int)CubeStepMatch.RD);
                  break;
               case OptimalCubeSteps.U2:
                  Solution.Add((int)CubeStepMatch.UL);
                  Solution.Add((int)CubeStepMatch.UL);
                  break;
               case OptimalCubeSteps.U:
                  Solution.Add((int)CubeStepMatch.UL);
                  break;
               case OptimalCubeSteps.Uc:
                  Solution.Add((int)CubeStepMatch.UR);
                  break;
               case OptimalCubeSteps.B2:
                  Solution.Add((int)CubeStepMatch.BA);
                  Solution.Add((int)CubeStepMatch.BA);
                  break;
               case OptimalCubeSteps.B:
                  Solution.Add((int)CubeStepMatch.BA);
                  break;
               case OptimalCubeSteps.Bc:
                  Solution.Add((int)CubeStepMatch.BC);
                  break;
               case OptimalCubeSteps.L2:
                  Solution.Add((int)CubeStepMatch.LD);
                  Solution.Add((int)CubeStepMatch.LD);
                  break;
               case OptimalCubeSteps.L:
                  Solution.Add((int)CubeStepMatch.LD);
                  break;
               case OptimalCubeSteps.Lc:
                  Solution.Add((int)CubeStepMatch.LU);
                  break;
               case OptimalCubeSteps.D2:
                  Solution.Add((int)CubeStepMatch.DR);
                  Solution.Add((int)CubeStepMatch.DR);
                  break;
               case OptimalCubeSteps.D:
                  Solution.Add((int)CubeStepMatch.DR);
                  break;
               case OptimalCubeSteps.Dc:
                  Solution.Add((int)CubeStepMatch.DL);
                  break;
               default:
                  throw new ArgumentOutOfRangeException();
            }
         }
         return Solution;
      }
   }
}
